/* 
 * scale implementation
 *
 * Choi Jonghwan<saxboy@saxboy.pe.kr>
 * 2006/04/26
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "scale.h"

/* FIXME:
 * there are chordal improvisation, as well as scale approach. 
 * patterns for chord is different with patterns for scales 
 */

struct __scale_def
{
	char *name; 
	SCALE_TYPE type; 
	int disp[MAX_NOTES_PER_SCALE]; 

} g_predefined_scales[] = 
{
{ "major", SCALE_MAJOR, {2,2,1,2,2,2,0, 1, 0, } }, /* W W H W W W H */
{ "minor", SCALE_MINOR, {2,1,2,2,1,2,0, 2, 0, } }, /* W W H W W W H */
{ "dominant", SCALE_DOMINANT, {2,2,1,2,2,1,0, 2, 0, } }, /* W W H W W W H */
{ "augmented", SCALE_WHOLETONE, {2,2,2,2,2,0, 2, 0, } }, /* W W W W W W */
{ "bebop", SCALE_BEBOP, {2,2,1,2,2,1,1,0, 1, 0, } }, /* W W H W W W H */
{ "blues", SCALE_BLUES, {3,2,1,1,3,0, 2, 0, } }, /* W W H W W W H */

{ "lydian_dominant", SCALE_LYDIAN_DOMINANT, {2,2,2,1,2,1,2, 0, } }, 


{ NULL,  SCALE_NONE, {0,} }
}; 

SCALE scale_New(void)
{
	SCALE s=NULL; 

	if ( (s=(SCALE)malloc(sizeof(_SCALE)) ) !=NULL)
	{
		memset(s, 0, sizeof(_SCALE)); 
		return s; 
	}
	return NULL; 
}

SCALE scale_NewFromDefinition(int abs_note, char *scaledef)
{
	SCALE s=NULL; 
	if ( (s=scale_New())!=NULL)
	{
		if ( scale_SetRoot(s, abs_note) > 0 )
		{
			if ( scale_ImportDefinition(s, scaledef) > 0 )
			{
				return s; 
			}
		}
	}
	return NULL; 
}

SCALE scale_NewPredefined(int abs_note, SCALE_TYPE type )
{
	SCALE s=NULL; 

	if ( (s=scale_New())!=NULL)
	{
		if ( scale_SetRoot(s, abs_note) > 0 )
		{
			if ( scale_SetType(s, type) > 0 )
			{
				return s; 
			}
		}
	}
	return NULL; 

}

SCALE scale_NewPredefinedWithMode(int abs_note, SCALE_TYPE type, int mode)
{
	SCALE s=NULL; 

	if ( (s=scale_New())!=NULL)
	{
		if ( scale_SetRoot(s, abs_note) > 0 )
		{
			if ( scale_SetTypeWithMode(s, type, mode) > 0 )
			{
				return s; 
			}
		}
	}
	return NULL; 

}

int scale_Delete(SCALE s)
{
	if (s!=NULL)
	{
		free(s); 
		return 1; 
	}
	return -1; 
}

static int _apply_scale_disp(SCALE s, int *disp)
{
	int i=0; 

	if (disp==NULL) return -1; 

	/* initialize note array */
	s->note_count =0; 
	memset( s->notes, 0, sizeof(int)*MAX_NOTES_PER_SCALE); 

	/* apply root note */
	s->notes[0] = s->root; 
	s->note_count++; 

	for (i=0; disp[i] >0; i++)
	{
		//printf("_apply_scale_disp(): s->notes[%d] = %d\n", i, s->notes[i]); 
		s->notes[i+1] = s->notes[i] + disp[i]; 
		s->note_count++; 
	}
	return 1; 
}

int scale_ImportDefinition(SCALE s, char *scaledef)
{
	if (s==NULL) return -1; 
	if (scaledef==NULL) return -1; 

	/* TODO: */

	return -1; 
}

int scale_SetType(SCALE s, SCALE_TYPE type)
{
	int i=0; 

#if 0
	if (s==NULL) return -1; 

	/* should have root note */
	if ( s->root == -1 ) return -1; 

	for (i=0; g_predefined_scales[i].name !=NULL; i++)
	{
		if ( g_predefined_scales[i].type == type )
		{
		  	printf("scale_SetType(): scale matched to [%s]\n", g_predefined_scales[i].name); 
			if ( _apply_scale_disp( s, g_predefined_scales[i].disp ) > 0)
			{
				s->type = type; 
				return 1; 
			}
		}
	}
#endif
	return scale_SetTypeWithMode(s, type, 1); 

	//return -1; 
}

int scale_SetTypeWithMode(SCALE s, SCALE_TYPE type, int mode)
{
	int i=0; 

	if (s==NULL) return -1; 
	/* should have root note */
	if ( s->root == -1 ) return -1; 

	/* search for the scale and copy scale displacement, generate mode 
	 */
	for (i=0; g_predefined_scales[i].name !=NULL; i++)
	{
		if ( g_predefined_scales[i].type == type )
		{
			if ( _apply_scale_disp( s, g_predefined_scales[i].disp ) > 0)
			{
			  	int xx=0; 
		  		int pos=mode-1; 
				int notes[MAX_NOTES_PER_SCALE]={0,}; 

				memcpy( notes, s->notes, sizeof(int)*s->note_count); 

				memcpy( &s->notes[0], &notes[pos], sizeof(int)*( s->note_count-pos) ); 
				memcpy( &s->notes[s->note_count-pos], notes, sizeof(int)*pos); 

				/* those notes should be one octave higher */
				for (xx=0; xx<pos; xx++)
				{
				  	s->notes[s->note_count-pos + xx] += 12; 
				}

				s->type = type; 
				s->mode = mode; 

				return 1; 
			}
		}
	}

	return -1; 
}

int scale_GetType(SCALE s)
{
	if (s==NULL) return -1; 
	return s->type; 
}

int scale_SetMode(SCALE s, int mode)
{
	if (s==NULL) return -1; 
	s->mode = mode; 
	return scale_SetTypeWithMode(s, s->type, mode); 
}

int scale_GetMode(SCALE s)
{
	if (s==NULL) return -1; 
	return s->mode; 
}

int scale_SetRoot(SCALE s, int abs_note)
{
	if (s==NULL) return -1; 

	/* if we already have type, recalc notes */
	s->root = abs_note; 

	if ( s->type!=SCALE_NONE && s->type!=SCALE_USER_DEFINED )
	{
		if ( scale_SetType(s, s->type) > 0 )
			return 1; 
	}

	/* FIXME: handle user defined case... */
	return 1; 
}

int scale_GetRoot(SCALE s)
{
	if (s==NULL) return -1; 
	return s->root; 
}

int scale_GetNoteCount(SCALE s)
{
	if (s==NULL) return -1; 
	return s->note_count; 
}

int scale_GetNote(SCALE s, int n)
{
	int pos = 0; 
	int octave = 0; 

	if (s==NULL) return -1; 

	/* if somebody requests 8th, 9th, etc... */
	if ( pos >= s->note_count )
	{
		pos = ( n % s->note_count); 
		octave = (int)( n / s->note_count ); 
	}

	return ( s->notes[n] + octave *12); 
}

int scale_AddNote(SCALE s, int abs_note)
{
	if (s==NULL) return -1; 
	s->notes[ s->note_count] = abs_note; 
	s->note_count++; 
	return 1; 
}

int scale_Print(SCALE s)
{
	int i=0; 

	if ( s==NULL) return -1; 

	printf("scale root: %d\n", scale_GetRoot(s)); 
	printf("scale mode: %d\n", scale_GetMode(s)); 

	for (i=0; i< scale_GetNoteCount(s); i++)
	{
		_NOTE _n; 
		_n.abs_note =scale_GetNote(s, i); 
		note_Print(&_n); 
	}
	printf("\n"); 
	return 1; 
}

int scale_ExportToString(SCALE s, char *buf, int buflen)
{
	/* TODO */
	return -1; 
}


//#ifdef __ENABLE_TEST__
#if 1

#include "note.h"

int main()
{
	SCALE s = scale_NewPredefinedWithMode(NOTE_F, SCALE_DOMINANT, 1); 

	if ( s==NULL) goto error; 
	scale_Print(s); 
	scale_Delete(s); 
	return 1; 

error:
	printf("error!!\n"); 
	return 0; 
}

#endif


